﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;

using vJine.Core.Base;
using vJine.Core.IoC;
using vJine.Core.ORM;

namespace vJine.Core.IO.Json {
    /// <summary>
    /// Json序列化、反序列化泛型帮助类
    /// </summary>
    /// <typeparam name="Tentity">实体类型</typeparam>
    public partial class JsonHelper<Tentity> {
        /// <summary>
        /// 实例化帮助类
        /// </summary>
        public JsonHelper() {
        }
        /// <summary>
        /// 序列化实体实例为XML字符串或文件
        /// </summary>
        /// <param name="entity">实体实例</param>
        /// <param name="json_file">如果不指定则序列化为Json字符串，否则序列化到指定的文件</param>
        /// <returns>XML字符串</returns>
        public static string ToString(Tentity entity, string json_file = null) {
            if(json_file == null) {
                MemoryStream mm = new MemoryStream();
                StreamWriter jsonWriter = new StreamWriter(mm, Encoding.UTF8);

                JsonHelper<Tentity>.helper(entity, jsonWriter);

                jsonWriter.Flush();

                return Encoding.UTF8.GetString(mm.ToArray());
            } else {
                FileStream jsonStream =
                    new FileStream(json_file, FileMode.OpenOrCreate | FileMode.Truncate);

                try {
                    StreamWriter jsonWriter = new StreamWriter(jsonStream, Encoding.UTF8);

                    JsonHelper<Tentity>.helper(entity, jsonWriter);

                    return null;
                } finally {
                    jsonStream.Close();
                }
            }
        }
        /// <summary>
        /// 序列化实体实例到指定的流
        /// </summary>
        /// <param name="entity">实体实例</param>
        /// <param name="json_stream">流</param>
        public static void ToString(Tentity entity, Stream json_stream) {
            StreamWriter jsonWriter = new StreamWriter(json_stream, Encoding.UTF8);

            JsonHelper<Tentity>.helper(entity, jsonWriter);
        }
        /// <summary>
        /// 序列化实体到指定的StreamWriter
        /// </summary>
        /// <param name="entity">实体实例</param>
        /// <param name="jsonWriter">StreamWriter</param>
        public static void ToString(Tentity entity, StreamWriter jsonWriter) {

            JsonHelper<Tentity>.helper(entity, jsonWriter);
        }
    }

    public partial class JsonHelper<Tentity> {
        static Type T_nullable = typeof(Nullable<>);
        static readonly Type Tobj = typeof(Tentity);

        static readonly Exec<Tentity, StreamWriter> helper = null;
        static JsonHelper() {
            try {
                JsonHelper<Tentity>.helper = JsonHelper<Tentity>.Create();
            } catch (Exception ex) {
                JsonHelper<Tentity>.helper = (Tentity Tjson, StreamWriter jsonStream) => {
                    throw ex;
                };
            }
        }
        /// <summary>
        /// 创建Json序列化代理
        /// </summary>
        /// <returns>Json序列化代理</returns>
        public static Exec<Tentity, StreamWriter> Create() {
            return JsonHelper<Tentity>.Create(Class<Tentity>.GetMap());
        }
        /// <summary>
        /// 以指定的属性集合创建Json序列化代理
        /// </summary>
        /// <param name="P">指定的属性集合</param>
        /// <returns>Json序列化代理</returns>
        public static Exec<Tentity, StreamWriter> Create(params Class<Tentity>.Property[] P) {
            DynamicMethod dmToString =
                new DynamicMethod("", Reflect.@void, new Type[] { JsonHelper<Tentity>.Tobj, typeof(StreamWriter) }, true);
            Dictionary<Type, LocalBuilder> lvs = new Dictionary<Type, LocalBuilder>();

            ILGenerator ilGen = dmToString.GetILGenerator();
            Call<LocalBuilder, Type> getVariable = (Type T_var) => {
                if (lvs.ContainsKey(T_var)) {
                    return lvs[T_var];
                }

                LocalBuilder lv = ilGen.DeclareLocal(T_var);
                lvs.Add(T_var, lv);

                return lv;
            };

            MethodInfo obj_to_string =
                typeof(object).GetMethod("ToString", Type.EmptyTypes);
            MethodInfo m_write_string =
                typeof(StreamWriter).GetMethod("Write", new Type[] { typeof(string) });

            Exec<Type> gen_write = (Type tObj) => {
                ilGen.Emit(OpCodes.Ldarg_1);
                Emit.Opc_Call(ilGen, JsonHelperCache.M[tObj]);
            };

            Exec<string> gen_write_string = (string V) => {
                ilGen.Emit(OpCodes.Ldarg_1);
                if(V != null) {
                    ilGen.Emit(OpCodes.Ldstr, V);
                }
                Emit.Opc_Call(ilGen, m_write_string);
            };

            #region gen_properties

            bool HasProperties = false;
            Exec _genPropertyToString = () => {
                for (int i = 0, len = P.Length; i < len; i++) {
                    Class<Tentity>.Property p_i = P[i];
                    if (p_i.IsXmlIgnore) {
                        continue;
                    }

                    if(!HasProperties) {
                        gen_write_string("{");
                    }
                    //Key
                    gen_write_string((HasProperties ? "," : "") + "\"" + p_i.Name + "\":");
                    //Value
                    Label lbNextProperty = ilGen.DefineLabel();
                    //get_value;
                    if (p_i.IsNullable) {
                        Label lbHasValue = ilGen.DefineLabel();
                        LocalBuilder lvNull = getVariable(p_i.pType);
                        Type Tnull = T_nullable.MakeGenericType(p_i.pTypeNull);

                        ilGen.Emit(OpCodes.Ldarg_0);
                        Emit.Opc_Call(ilGen, p_i.get);
                        ilGen.Emit(OpCodes.Stloc, lvNull);
                        ilGen.Emit(OpCodes.Ldloca, lvNull);
                        ilGen.Emit(OpCodes.Call, Tnull.GetProperty("HasValue").GetGetMethod());
                        ilGen.Emit(OpCodes.Brtrue, lbHasValue);

                        gen_write_string(JsonHelper.NULL);
                        ilGen.Emit(OpCodes.Br, lbNextProperty);

                        ilGen.MarkLabel(lbHasValue);
                        ilGen.Emit(OpCodes.Ldloca, lvNull);
                        ilGen.Emit(OpCodes.Call, Tnull.GetProperty("Value").GetGetMethod());
                    } else {
                        ilGen.Emit(OpCodes.Ldarg_0);
                        Emit.Opc_Call(ilGen, p_i.get);
                    }

                    Type p_type = p_i.IsNullable ? p_i.pTypeNull : p_i.pType;
                    if (JsonHelperCache.M.ContainsKey(p_type)) {
                        gen_write(p_type);
                    } else if(p_i.IsEnum) {
                        ilGen.Emit(OpCodes.Box, p_type);
                        ilGen.Emit(OpCodes.Callvirt, obj_to_string);
                        LocalBuilder lv_enum = getVariable(Reflect.@string);
                        ilGen.Emit(OpCodes.Stloc, lv_enum);

                        gen_write_string("\"");
                        {
                            ilGen.Emit(OpCodes.Ldarg_1);
                            ilGen.Emit(OpCodes.Ldloc, lv_enum);
                            Emit.Opc_Call(ilGen, m_write_string);
                        }
                        gen_write_string("\"");
                    } else {
                        MethodInfo json_tostring =
                            Tthis.MakeGenericType(p_type).GetMethod("ToString", new Type[] { p_type, typeof(StreamWriter) });

                        ilGen.Emit(OpCodes.Ldarg_1);
                        Emit.Opc_Call(ilGen, json_tostring);
                    }
                    ilGen.MarkLabel(lbNextProperty);

                    HasProperties = true;
                }
            };
            #endregion gen_properties

            Type t_Obj = JsonHelper<Tentity>.Tobj;
            if (t_Obj == Reflect.@object) {
                ilGen.Emit(OpCodes.Ldarg_0);
                ilGen.Emit(OpCodes.Ldarg_1);
                Emit.Opc_Call(ilGen, object_toString);
            } else if (JsonHelperCache.M.ContainsKey(t_Obj)) {
                ilGen.Emit(OpCodes.Ldarg_0);
                gen_write(t_Obj);
            } else if(Reflect.IsEnum(t_Obj)) {
                gen_write_string("\"");
                {
                    ilGen.Emit(OpCodes.Ldarg_1);
                    ilGen.Emit(OpCodes.Ldarg_0);
                    ilGen.Emit(OpCodes.Box, t_Obj);
                    ilGen.Emit(OpCodes.Callvirt, obj_to_string);
                    Emit.Opc_Call(ilGen, m_write_string);
                }
                gen_write_string("\"");
            } else {
                Label lbIsNotNull = ilGen.DefineLabel();
                {//if(objT == null) return "null";
                    ilGen.Emit(OpCodes.Ldarg_0);
                    ilGen.Emit(OpCodes.Ldnull);
                    ilGen.Emit(OpCodes.Ceq);

                    ilGen.Emit(OpCodes.Brfalse, lbIsNotNull);
                    {
                        gen_write_string(JsonHelper.NULL);
                        ilGen.Emit(OpCodes.Ret);
                    }
                } // else {
                {
                    ilGen.MarkLabel(lbIsNotNull);
                }

                _genPropertyToString();

                if (Reflect.IsArray(t_Obj)) {
                    gen_write_string( HasProperties ? ",\"Items\":" : "");

                    MethodInfo list_tostring =
                                list_toString.MakeGenericMethod(t_Obj.GetElementType());

                    ilGen.Emit(OpCodes.Ldarg_0);
                    ilGen.Emit(OpCodes.Ldarg_1);
                    Emit.Opc_Call(ilGen, list_tostring);
                } else if (Reflect.IsList(t_Obj)) {
                    gen_write_string(HasProperties ? ",\"Items\":" : "");

                    MethodInfo list_tostring =
                                list_toString.MakeGenericMethod(Reflect.GetGenericArgs(JsonHelper<Tentity>.Tobj));

                    ilGen.Emit(OpCodes.Ldarg_0);
                    ilGen.Emit(OpCodes.Ldarg_1);
                    Emit.Opc_Call(ilGen, list_tostring);
                } else if(Reflect.IsDictionary(t_Obj)) {
                    gen_write_string(HasProperties ? ",\"Items\":" : "");

                    MethodInfo dict_tostring =
                                dict_toString.MakeGenericMethod(Reflect.GetGenericArgs(JsonHelper<Tentity>.Tobj));

                    ilGen.Emit(OpCodes.Ldarg_0);
                    ilGen.Emit(OpCodes.Ldarg_1);
                    Emit.Opc_Call(ilGen, dict_tostring);
                }

                if(HasProperties) {
                    gen_write_string("}");
                }
            }

            ilGen.Emit(OpCodes.Ret);
            return dmToString.CreateDelegate(typeof(Exec<Tentity, StreamWriter>)) as Exec<Tentity, StreamWriter>;
        }

        static Type Tthis = typeof(JsonHelper<Tentity>).GetGenericTypeDefinition();
        static MethodInfo object_toString = new Exec<object, StreamWriter>(ToString).Method;
        static void ToString(object jsonObject, StreamWriter jsonWriter) {
            if (jsonObject == null) {
                jsonWriter.Write(JsonHelper.NULL);
                return;
            }

            JsonHelperCache.ToString(jsonObject.GetType())(jsonObject, jsonWriter);
        }

        static MethodInfo list_toString =
            new Exec<IList<JsonHelper<Tentity>>, StreamWriter>(ToString).Method.GetGenericMethodDefinition();
        static void ToString<V>(IList<V> list, StreamWriter jsonWriter) {
            if (list == null) {
                jsonWriter.Write(JsonHelper.NULL);
                return;
            }
            Exec<V, StreamWriter> list_helper = JsonHelper<V>.helper;

            jsonWriter.Write("[");
            for (int i = 0, len = list.Count; i < len; i++) {
                if (i > 0) {
                    jsonWriter.Write(",");
                }

                list_helper(list[i], jsonWriter);
            }
            jsonWriter.Write("]");
        }

        static MethodInfo dict_toString =
            new Exec<Dictionary<JsonHelper<Tentity>, JsonHelper<Tentity>>, StreamWriter>(ToString).Method.GetGenericMethodDefinition();
        static void ToString<K, v>(Dictionary<K, v> dict, StreamWriter jsonWriter) {

            if (dict == null) {
                jsonWriter.Write(JsonHelper.NULL);
                return;
            }
            Exec<K, StreamWriter> key_helper = JsonHelper<K>.helper;
            Exec<v, StreamWriter> value_helper = JsonHelper<v>.helper;

            int index = 0, len = dict.Keys.Count;
            jsonWriter.Write("[");
            foreach (KeyValuePair<K, v> kv in dict) {
                if (index > 0) {
                    jsonWriter.Write(",");
                }

                key_helper(kv.Key, jsonWriter);
                jsonWriter.Write(",");
                value_helper(kv.Value, jsonWriter);

                index += 1;
            }
            jsonWriter.Write("]");
        }
    }
}
